package ink.durian.web;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @author: Durian
 * @date: 2021-10-20 21:28
 */
@Controller
@RequestMapping("/oauth2")
public class AuthController {
    // TODO 将token存储的方式改为带过期的缓存,相关token绑定信息同样如此
    /** app列表 */
    private static final Map<String, Dict> appList = new HashMap<>();
    /** app信息 */
    private static final Dict appMap = new Dict();
    /** code bind appid and user */
    private static final TimedCache<String, String> codeMap = CacheUtil.newTimedCache(5 * 60 * 1000);
    /** code bind scope*/
    private static final Map<String, String[]> codeScopeMap = new HashMap<>();
    /** 存储发放的token */
    private static final Map<String, String> tokenMap = new HashMap<>();
    /** 存储refreshToken */
    private static final Map<String, String> refreshTokenMap = new HashMap<>();
    /** token绑定scope */
    private static final Map<String, String[]> tokenScopeMap = new HashMap<>();
    /** refreshToken bind scope */
    private static final Map<String, String[]> refreshTokenScopeMap = new HashMap<>();
    /** token 绑定refreshToken */
    private static final Map<String, String> refreshTokenMapToken = new HashMap<>();
    // TODO 添加授权服务获取用户信息环节
    private static final String TEST_USER = "Durian";

    // 模拟数据
    static {

        appMap.put("appId", "10001");
        appMap.put("secret", "JFEDHSFUHDHFILSFU");
        appMap.put("redirect_uri", "http://localhost:9000/app/index/authRecall");
        appMap.put("scope", CollUtil.newArrayList("user_base_info", "Order Info"));

        appList.put("10001", appMap);
    }

    @GetMapping("/authorize")
    public ModelAndView authPage(HttpServletRequest request) {
        ModelAndView mv = new ModelAndView();
        // 获取code是不需要secret
        String appId = request.getParameter("appId");
        String redirect_uri = request.getParameter("redirect_uri");
        String scope = request.getParameter("scope");
        String response_type = request.getParameter("response_type");
        // 0. 验证appid是否存在
        if (appList.get(appId) == null) {
            mv.setView(new MappingJackson2JsonView());
            mv.addObject("10001: APP未注册");
            return mv;
        }
        // 1. 验证回调地址合法性
        if (!StrUtil.equals(appList.get(appId).getStr("redirect_uri"), redirect_uri)) {
            mv.setView(new MappingJackson2JsonView());
            mv.addObject("10003: redirect_uri与后台配置不一致");
            return mv;
        }
        // 2. 验证权限范围
        if (!checkScope(scope)) {
            mv.setView(new MappingJackson2JsonView());
            mv.addObject("10005: 此APP并没有这些scope的权限");
            return mv;
        }
        // 3. 跳转授权页面
        mv.addObject("scopes", appList.get(appId).get("scope"));
        mv.addObject("username", TEST_USER);
        mv.addObject("appId", appId);
        mv.addObject("response_type", response_type);
        mv.addObject("redirect_uri", redirect_uri);
        mv.setViewName("auth");
        return mv;
    }

    @PostMapping("/authorize")
    public ModelAndView authorize(HttpServletRequest request) {
        ModelAndView mv = new ModelAndView();
        String[] scope = request.getParameterValues("scope");
        String response_type = request.getParameter("response_type");
        String appId = request.getParameter("appId");
        String redirect_uri = request.getParameter("redirect_uri");
        // 验证
        if (!checkScope(scope)) {
            mv.setView(new MappingJackson2JsonView());
            mv.addObject("10005: 此APP并没有这些scope的权限");
            return mv;
        }
        // 获取授权码流程
        if (StrUtil.equals(response_type, "code")) {
            // 授权码code需要与appId, user 关联, 一个授权码表示一个用户给某个第三方软件授权
            String code = generateCode(appId, TEST_USER);
            // 授权码与code绑定
            codeScopeMap.put(code, scope);
            // 带上code重定向至第三方APP回调地址
            mv.setViewName(String.format("redirect:%s?code=%s", redirect_uri, code));
            return mv;
        }
        return mv;
    }

    @PostMapping("/access_token")
    @ResponseBody
    public Map<String, Object> accessToken(HttpServletRequest request) {
        String appId = request.getParameter("appId");
        String grant_type = request.getParameter("grant_type");
        String secret = request.getParameter("secret");
        String code = request.getParameter("code");

        // 处理授权码流程中的 颁发访问令牌 环节
        if (StrUtil.equals(grant_type, "authorization_code")) {
            // 获取授权码是第三方发送的post请求,需要验证secret了, 此处应该使用https加密
            if (appList.get(appId) == null || !appList.get(appId).get("appId").equals(appId)) {
                // appid异常
                return MapUtil.builder(new HashMap<String, Object>())
                        .put("errcode", 10001)
                        .put("errMsg", "Invalid AppId")
                        .build();
            }
            if (!StrUtil.equals(appList.get(appId).getStr("secret"), secret)) {
                // secret异常
                return MapUtil.builder(new HashMap<String, Object>())
                        .put("errcode", 10002)
                        .put("errMsg", "Invalid secret")
                        .build();
            }
            // 验证code
            if (!isExistCode(code)) {
                return MapUtil.builder(new HashMap<String, Object>())
                        .put("errcode", 40029)
                        .put("errMsg", "Invalid code")
                        .build();
            }
            // code使用完后一定要删除
            codeMap.remove(code);

            // 生成accessToken
            String access_token = generateAccessToken(appId, TEST_USER);
            // 绑定授权范围
            tokenScopeMap.put(access_token, codeScopeMap.get(code));
            // 生成refreshToken
            String refresh_token = generateRefreshToken(appId, TEST_USER);
            // 绑定refreshToken授权范围
            refreshTokenScopeMap.put(refresh_token, codeScopeMap.get(code));
            // accessToken绑定refreshToken  用于刷新令牌时取到旧的token
            refreshTokenMapToken.put(refresh_token, access_token);

            return MapUtil.builder(new HashMap<String, Object>())
                    .put("access_token", access_token)
                    .put("scope", codeScopeMap.get(code))
                    .put("refresh_token", refresh_token)
                    .put("expire_in", 1)
                    .build();
        }
        return MapUtil.builder(new HashMap<String, Object>())
                .put("errcode", 40000)
                .put("errmsg", "Invalid grant_type")
                .build();
    }

    @PostMapping("/refresh_token")
    @ResponseBody
    public Map<String, Object> refreshToken(HttpServletRequest request) {
        String appId = request.getParameter("appId");
        String grant_type = request.getParameter("grant_type");
        String refresh_token = request.getParameter("refresh_token");

        // 处理刷新令牌请求环节
        if (StrUtil.equals(grant_type, "refresh_token")) {
            if (appList.get(appId) == null || !appList.get(appId).get("appId").equals(appId)) {
                // appid异常
                return MapUtil.builder(new HashMap<String, Object>())
                        .put("errcode", 10001)
                        .put("errMsg", "Invalid AppId")
                        .build();
            }
            // 判断refresh_token是否正确 在判断token与绑定的appid user是否一致
            if (!refreshTokenMap.containsKey(refresh_token) || !refreshTokenMap.get(refresh_token).startsWith(appId + "|" + TEST_USER)) {
                return MapUtil.builder(new HashMap<String, Object>())
                        .put("errcode", 10005)
                        .put("errMsg", "Invalid refresh_token")
                        .build();
            }
            // 生成新的token和refresh_token,同时无效掉旧的
            String old_access_token = refreshTokenMapToken.get(refresh_token);
            // 生成accessToken
            String new_access_token = generateAccessToken(appId, TEST_USER);
            // 绑定授权范围
            tokenScopeMap.put(new_access_token, refreshTokenScopeMap.get(refresh_token));
            // 生成refreshToken
            String new_refresh_token = generateRefreshToken(appId, TEST_USER);
            // 绑定refreshToken授权范围
            refreshTokenScopeMap.put(new_refresh_token, refreshTokenScopeMap.get(refresh_token));
            // accessToken绑定refreshToken
            refreshTokenMapToken.put(new_refresh_token, new_access_token);

            // 移除旧的access_token和refresh_token信息
            tokenMap.remove(old_access_token);
            tokenScopeMap.remove(old_access_token);
            refreshTokenMap.remove(refresh_token);
            refreshTokenScopeMap.remove(refresh_token);
            refreshTokenMapToken.remove(refresh_token);

            return MapUtil.builder(new HashMap<String, Object>())
                    .put("access_token", new_access_token)
                    .put("scope", refreshTokenScopeMap.get(new_refresh_token))
                    .put("refresh_token", new_refresh_token)
                    .put("expire_in", 1)
                    .build();
        }
        return MapUtil.builder(new HashMap<String, Object>())
                .put("errcode", 40000)
                .put("errmsg", "Invalid grant_type")
                .build();
    }

    private String generateAccessToken(String appId, String testUser) {
        String accessToken = IdUtil.simpleUUID();
        long expire_in = 1;
        tokenMap.put(accessToken, appId + "|" + testUser + "|" + System.currentTimeMillis() + "|" + expire_in);
        return accessToken;
    }

    private String generateRefreshToken(String appId, String testUser) {
        String refreshToken = IdUtil.simpleUUID();
        long expire_in = 10;
        refreshTokenMap.put(refreshToken, appId + "|" + testUser + "|" + System.currentTimeMillis() + "|" + expire_in);
        return refreshToken;
    }

    private boolean isExistCode(String code) {
        return codeMap.containsKey(code);
    }

    /**
     * 验证权限范围
     */
    private boolean checkScope(String... scope) {
        // String --> List
        return true;
    }

    private String generateCode(String appId, String user) {
        Random r = new Random();
        StringBuilder strb = new StringBuilder();
        for (int i = 0; i < 8; i++) {
            strb.append(r.nextInt(10));
        }
        String code = strb.toString();
        // TODO 仅作为演示用，实际这应该是一个全局内存数据库，有效期官方建议是10分钟
        codeMap.put(code, appId + "|" + user + "|" + System.currentTimeMillis());
        return code;
    }

}
